﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Noris.Tools.TraceVisualiser.Support;
using Djs.Tools.XmlPersistor;

namespace Noris.Tools.TraceVisualiser.Components
{
    #region CLASS RowFilter : Řádkový filtr. Obsahuje položky, které filtrují jednotlivé sloupce.
    /// <summary>
    /// RowFilter : Řádkový filtr, datová vrstva.
	/// Obsahuje položky, které filtrují jednotlivé sloupce.
    /// </summary>
    public class RowFilter : IEnumerable<RowFilterItem>, IDisposable
    {
        #region Konstrukce, základní metody a property
		/// <summary>
		/// Konstruktor
		/// </summary>
        public RowFilter()
        {
            this._Items = new List<RowFilterItem>();
        }
        /// <summary>
        /// Disposable
        /// </summary>
        public void Dispose()
        {
            this._Items.Clear();
            this._Items = null;
        }
        private List<RowFilterItem> _Items;
        /// <summary>
        /// Přidá do filtru další položku.
        /// Pokud je na vstupu null, anebo položka IsEmpty, pak ji nepřidá.
        /// </summary>
        /// <param name="item"></param>
        public void Add(RowFilterItem item)
        {
            if (item == null || item.IsEmpty)
                return;
            this._Items.Add(item);
        }
        /// <summary>
        /// Počet položek filtru
        /// </summary>
        public int Count { get { return _Items.Count; } }
        /// <summary>
        /// Obsahuje true, když je celý filtr prázdný (není podle čeho filtrovat)
        /// </summary>
        public bool IsEmpty
        {
            get
            {
                if (this.HasExplicitFilter) return false;

                if (this._Items == null || this._Items.Count == 0) return true;

                foreach (RowFilterItem item in this._Items)
                {
                    if (!item.IsEmpty) return false;
                }
                return true;
            }
        }
        /// <summary>
        /// Položka filtru na daném indexu
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public RowFilterItem this[int index]
        {
            get { return _Items[index]; }
        }
		/// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
        public override string ToString()
        {
            return "RowFilter: Count=" + this.Count.ToString() + ", Items={" + this._Items.ToStringList() + "}";
        }
        /// <summary>
        /// Vrátí fyzický klon sebe sama.
        /// </summary>
        /// <returns></returns>
        public RowFilter Clone()
        {
            RowFilter clone = (RowFilter)this.MemberwiseClone();
            clone._Items = new List<RowFilterItem>();
            this._Items.ForEach(f => clone._Items.Add(f.Clone()));
            return clone;
        }
        #endregion
        #region Static services: detekce tokenu v textu
        /// <summary>
        /// Metoda detekuje, zda v daném textu (vstup z textboxu) nejsou na začátku uvedeny znaky, které představují explicitně uvedenou filtrační podmínku.
        /// Detekují se znaky: menší, větší, rovnítko, procento, vykřičník.
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public static RowFilterConditionType DetectConditionToken(ref string text)
        {
            if (String.IsNullOrEmpty(text))
                return RowFilterConditionType.None;

            string trim = text.TrimStart();
            int length = trim.Length;

            // ==
            if (length > 2 && trim.StartsWith("=="))
            {
                text = trim.Substring(2);
                return RowFilterConditionType.Equal;
            }

            // !=
            if (length > 2 && trim.StartsWith("!="))
            {
                text = trim.Substring(2);
                return RowFilterConditionType.NonEqual;
            }

            // %
            if (length > 1 && trim.StartsWith("%"))
            {
                text = trim.Substring(1);
                return RowFilterConditionType.Contain;
            }

            // !%
            if (length > 2 && trim.StartsWith("!%"))
            {
                text = trim.Substring(2);
                return RowFilterConditionType.NonContain;
            }

            // =
            if (length > 1 && trim.StartsWith("="))
            {
                text = trim.Substring(1);
                return RowFilterConditionType.BeginWith;
            }

            // !
            if (length > 1 && trim.StartsWith("!"))
            {
                text = trim.Substring(1);
                return RowFilterConditionType.NonBeginWith;
            }

            // <=
            if (length > 2 && trim.StartsWith("<="))
            {
                text = trim.Substring(2);
                return RowFilterConditionType.LowerOrEqual;
            }

            // <
            if (length > 1 && trim.StartsWith("<"))
            {
                text = trim.Substring(1);
                return RowFilterConditionType.Lower;
            }

            // >=
            if (length > 2 && trim.StartsWith(">="))
            {
                text = trim.Substring(2);
                return RowFilterConditionType.GreaterOrEqual;
            }

            // >
            if (length > 1 && trim.StartsWith(">"))
            {
                text = trim.Substring(1);
                return RowFilterConditionType.Greater;
            }

			// ;
			if (length > 1 && trim.StartsWith(";"))
			{
				text = trim.Substring(1);
				return RowFilterConditionType.InList;
			}

			return RowFilterConditionType.None;
        }
        #endregion
        #region IEnumerable<RowFilterItem> Members
        IEnumerator<RowFilterItem> IEnumerable<RowFilterItem>.GetEnumerator()
        {
            return _Items.GetEnumerator();
        }
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _Items.GetEnumerator();
        }
        #endregion
        #region Explicitní filtr
        /// <summary>
        /// Tento objekt má explicitní filtr
        /// </summary>
        public bool HasExplicitFilter { get { return (this._ExplicitRowList != null); } }
        /// <summary>
        /// Seznam řádků, které vyhovují tomuto filtru.
        /// Jde o explicitní filtr.
        /// Používá jej sama aplikace (typicky: Plugin-funkce nad seznamem) při tvorbě filtru na explicitní řádky.
        /// Takový filtr je možno aplikovat z resultu funkce do Gridu, a v gridu je možno jej odebrat kliknutím na ikonu ClearFilter.
        /// Filtru na závěr vyhoví pouze řádky, jejichž RowGId je uveden v tomto seznamu.
        /// Pokud tento seznam je null, pak se explicitní filtr neaplikuje (this.HasExplicitFilter = false).
        /// </summary>
        public List<GID> ExplicitRowList
        {
            get { return this._ExplicitRowList; }
            set { this._ExplicitRowList = value; this._ExplicitRowDict = null; }
        }
        /// <summary>
        /// Vrací příznak, zda daný GID existuje v seznamu.
        /// </summary>
        /// <param name="gID"></param>
        /// <returns></returns>
        protected bool ContainExplicitRow(GID gID)
        {
            if (this._ExplicitRowList == null) return true;        // Neexistuje seznam _ExplicitRowList: vracím true

            // Máme List, ale nemáme Dict (to je po změně Listu). Anebo po změně počtu položek v Listu. Musím si nově sestavit Dict:
            if (this._ExplicitRowDict == null || this._ExplicitRowList.Count != this._ExplicitRowListCount)
            {
                this._ExplicitRowDict = new Dictionary<GID, object>();
                foreach (GID g in this._ExplicitRowList)
                {
                    if (!this._ExplicitRowDict.ContainsKey(g))
                        this._ExplicitRowDict.Add(g, null);
                }
                this._ExplicitRowListCount = this._ExplicitRowList.Count;
            }

            return this._ExplicitRowDict.ContainsKey(gID);
        }
        private List<GID> _ExplicitRowList;
        private int _ExplicitRowListCount;
        private Dictionary<GID, object> _ExplicitRowDict;
        /// <summary>
        /// Testuje řádek na explicitní filtr.
        /// Pokud this objekt nemá ExplicitFilter, pak tato metoda vrací true.
        /// Pokud ale je HasExplicitFilter = true, pak tato metoda vrací true pouze pro GID, které jsou uvedeny v seznamu explicitních řádků.
        /// </summary>
        /// <param name="gID"></param>
        /// <returns></returns>
        internal bool RowIsExplicitConform(GID gID)
        {
            return this.ContainExplicitRow(gID);
        }
        #endregion
        #region Persistence
        /// <summary>
		/// Property FilterItems je jediná persistovaná.
		/// </summary>
		[PersistingEnabled(true)]
		[PropertyName("filters")]
		private List<RowFilterItem> FilterItems { get { return this._Items; } set { this._Items = value; } }
		#endregion
    }
    #endregion
    #region CLASS RowFilterItem : Jedna položka filtru.
    /// <summary>
    /// RowFilterItem : Jedna položka filtru, datová vrstva.
    /// </summary>
    public class RowFilterItem
    {
        #region Konstrukce a základní metody
		/// <summary>
		/// Aby bylo možno RowFilterItem persistovat, musí mít bezparametrický konstruktor (může být private).
		/// </summary>
        private RowFilterItem()
        { }
		/// <summary>
		/// Vytvoří a vrátí prvek filtru RowFilterItem.
		/// </summary>
		/// <param name="index"></param>
		/// <param name="itemName"></param>
		/// <param name="condition"></param>
		/// <param name="itemValue"></param>
		/// <returns></returns>
        public static RowFilterItem Create(int index, string itemName, RowFilterConditionType condition, string itemValue)
        {
			RowFilterItem item = new RowFilterItem() { ColumnIndex = index, ItemName = itemName, ItemValue = itemValue, Condition = condition };
            return item;
        }
        /// <summary>
        /// Index sloupce, v němž je filtr umístěn a zadán.
        /// </summary>
        [PersistingEnabled(false)]
		public int ColumnIndex { get; private set; }
        /// <summary>
        /// Klíčový název sloupce (nikoli titulek), v němž je filtr umístěn a zadán. Identifikace.
        /// </summary>
		[PersistingEnabled(false)]
		public string ItemName { get; private set; }
        /// <summary>
        /// Druh podmínky.
        /// </summary>
		[PersistingEnabled(false)]
		public RowFilterConditionType Condition { get; private set; }
        /// <summary>
        /// Hodnota filtru.
        /// </summary>
		[PersistingEnabled(false)]
		public string ItemValue { get; private set; }
		/// <summary>
		/// Hashcode
		/// </summary>
		/// <returns></returns>
        public override int GetHashCode()
        {
            return this.ItemName.GetHashCode() ^ this.Condition.GetHashCode() ^ this.ItemValue.GetHashCode();
        }
		/// <summary>
		/// Equals
		/// </summary>
		/// <param name="obj"></param>
		/// <returns></returns>
        public override bool Equals(object obj)
        {
            RowFilterItem other = (obj as RowFilterItem);
            if (other == null) return false;

            // Pokud jsou oba objekty prázdné, pak jsou shodné (nemají vliv na filtr):
            if (this.IsEmpty && other.IsEmpty) return true;
            if (this.IsEmpty != other.IsEmpty) return false;        // Pokud jeden je prázdný a druhý ne, pak jsou odlišné.

            return (String.Equals(this.ItemName, other.ItemName)
                && this.Condition == other.Condition
                && String.Equals(this.ItemValue, other.ItemValue));
        }
		/// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
        public override string ToString()
        {
            return
                (this.ItemName != null ? "\"" + this.ItemName + "\"" : "null") + " " +
                this.Condition.ToString() + " " +
                (this.ItemValue != null ? "(" + this.ItemValue + ")" : "null");
        }
		/// <summary>
		/// Obsahuje true, pokud tento prvek neobsahuje data (nefiltruje se podle něj)
		/// </summary>
        public bool IsEmpty { get { return !_ContainData(); } }
        /// <summary>
        /// Vrací true, když položka filtru obsahuje nějaká data
        /// </summary>
        /// <returns></returns>
        private bool _ContainData()
        {
            switch (Condition)
            {
                case RowFilterConditionType.None:
                    return false;
                case RowFilterConditionType.Empty:
                case RowFilterConditionType.NonEmpty:
                    return true;
                case RowFilterConditionType.Equal:
                case RowFilterConditionType.BeginWith:
                case RowFilterConditionType.NonBeginWith:
                case RowFilterConditionType.Contain:
                case RowFilterConditionType.NonContain:
                case RowFilterConditionType.NonEqual:
                case RowFilterConditionType.LowerOrEqual:
                case RowFilterConditionType.Lower:
                case RowFilterConditionType.GreaterOrEqual:
                case RowFilterConditionType.Greater:
                case RowFilterConditionType.InList:
                case RowFilterConditionType.InRange:
                    return (!String.IsNullOrEmpty(this.ItemValue));
            }
            return false;
        }
        /// <summary>
        /// Vrátí fyzický klon sebe sama.
        /// </summary>
        /// <returns></returns>
        public RowFilterItem Clone()
        {
            RowFilterItem clone = (RowFilterItem)this.MemberwiseClone();
            return clone;
        }
        #endregion
		#region Persistence
		/// <summary>
		/// Tato property je jediná persistovaná z celého filtru, je private = uživatel ji nevidí.
		/// Její význam je právě v persistenci filtru.
		/// Hodnota řádkového filtru se ukládá mj. společně s pozicí (Bod zájmu).
		/// Setování hodnoty nevyvolá event Changed.
		/// </summary>
		private string Filter
		{
			get { return this._GetFilter(); }
			set { this._SetFilter(value); }
		}
		private string _GetFilter()
		{
			string filter = this.ItemName;
			if (!this.IsEmpty)
			{
				filter += ":" + Convertor.EnumToString(this.Condition);
				if (!String.IsNullOrEmpty(this.ItemValue))
					filter += ":" + this.ItemValue;
			}
			return filter;
		}
		private void _SetFilter(string filter)
		{
			string text = filter;
            this.ItemName = "";
			this.Condition = RowFilterConditionType.BeginWith;
			this.ItemValue = "";

			if (!String.IsNullOrEmpty(text))
			{
				string name = Convertor.StringCutOff(ref text, ":");
				if (name.Length > 0)
                {
                    this.ItemName = name;
					string cond = Convertor.StringCutOff(ref text, ":");
					this.Condition = Convertor.StringToEnum<RowFilterConditionType>(cond, RowFilterConditionType.BeginWith);
					this.ItemValue = text;
				}
			}
		}
		#endregion
        #region Filtrování
        /// <summary>
        /// Metoda zjistí, zda daná hodnota (načtená z řádku) odpovídá filtru, pro odpovídající sloupec (data == filtr).
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool ValueIsConform(string value)
        {
            string filterValue = (this.ItemValue == null ? "" : this.ItemValue);
            string dataValue = (value == null ? "" : value);
            switch (this.Condition)
            {
                case RowFilterConditionType.Equal:
                    return (String.Equals(filterValue.Trim(), dataValue.Trim(),  StringComparison.CurrentCultureIgnoreCase));
                case RowFilterConditionType.BeginWith:
                    return (dataValue.Trim().StartsWith(filterValue.Trim(), StringComparison.CurrentCultureIgnoreCase));
                case RowFilterConditionType.NonBeginWith:
                    return (!dataValue.Trim().StartsWith(filterValue.Trim(), StringComparison.CurrentCultureIgnoreCase));
                case RowFilterConditionType.Contain:
                    return _FilterContain(dataValue, filterValue);
                case RowFilterConditionType.NonContain:
                    return !_FilterContain(dataValue, filterValue);
                case RowFilterConditionType.NonEqual:
                    return (!String.Equals(filterValue.Trim(), dataValue.Trim(),  StringComparison.CurrentCultureIgnoreCase));
                case RowFilterConditionType.LowerOrEqual:
                    return (_FilterCompare(dataValue, filterValue) <= 0);
                case RowFilterConditionType.Lower:
                    return (_FilterCompare(dataValue, filterValue) < 0);
                case RowFilterConditionType.GreaterOrEqual:
                    return (_FilterCompare(dataValue, filterValue) >= 0);
                case RowFilterConditionType.Greater:
                    return (_FilterCompare(dataValue, filterValue) > 0);
                case RowFilterConditionType.Empty:
                    return String.IsNullOrWhiteSpace(dataValue);
                case RowFilterConditionType.NonEmpty:
                    return !String.IsNullOrWhiteSpace(dataValue);
                case RowFilterConditionType.InList:
					return _FilterIsInList(dataValue, filterValue);
                case RowFilterConditionType.InRange:
                    return true;
            }
            return true;;
        }
        #region FILTER CONTAIN
        /// <summary>
        /// Obsahuje hodnota dataValue některý text z filterValue?
        /// </summary>
        /// <param name="dataValue"></param>
        /// <param name="filterValue"></param>
        /// <returns></returns>
        private bool _FilterContain(string dataValue, string filterValue)
        {
            // Jednotlivé položky:
            List<_FilterContainItem> items = this._GetFilterContainItems(filterValue);
            foreach (_FilterContainItem ci in items)
            {
                if (!ci.ValueIsConform(dataValue)) return false;
            }
            return true;
        }
        /// <summary>
        /// Vrátí seznam složek filtru Contain. Seznam vytvoří jen jedenkrát, pak jej uloží do paměti a vrací jej z paměti.
        /// </summary>
        /// <param name="filterValue"></param>
        /// <returns></returns>
        private List<_FilterContainItem> _GetFilterContainItems(string filterValue)
        {
            // Jdeme do cache?
            if (this._FilterContainItemLast != null && this._FilterContainItemLast == filterValue && this._FilterContainItems != null)
                return this._FilterContainItems;

            // Zpracujeme text do cache:
            this._FilterContainItems = new List<_FilterContainItem>();
            bool needContain = true;                        // Hodnota NeedContain pro příští prvek (oddělovač nám oddělí prvek předešlý, a nadeklaruje needContain pro prvek příští).
            string filter = filterValue;
            while (filter != null && filter.Length > 0)
            {
                int atN = filter.IndexOf("!%");             // Hledáme oddělovač !% ... "Neobsahuje"
                int atY = filter.IndexOf("%");              // Hledáme oddělovač %  ... "Obsahuje"
                if (atN >= 0 && (atY < 0 || atN < atY))     // Našli jsme !%, a je tam (buď jediný, anebo je dříve než %)
                {
                    if (atN > 0)
                        this._FilterContainItems.Add(new _FilterContainItem(needContain, filter.Substring(0, atN)));
                    filter = (filter.Length > atN + 2 ? filter.Substring(atN + 2) : "");
                    needContain = false;           // Pokud některý prvek ja uvozen oddělovačem !%, pak tento prvek nesmí být v textu obsažen.
                }
                else if (atY >= 0)
                {
                    if (atY > 0)
                        this._FilterContainItems.Add(new _FilterContainItem(needContain, filter.Substring(0, atY)));
                    filter = (filter.Length > atY + 1 ? filter.Substring(atY + 1) : "");
                    needContain = true;            // Pokud některý prvek ja uvozen oddělovačem %, pak tento prvek musí být v textu obsažen.
                }
                else
                {
                    this._FilterContainItems.Add(new _FilterContainItem(needContain, filter));
                    filter = "";
                }
            }
            this._FilterContainItemLast = filterValue;

            return this._FilterContainItems;
        }
        /// <summary>
        /// Hodnota filterValue rozdělená na jednotlivé položky.
        /// filterValue může obsahovat jeden text ("abc"), pak zjistíme, zda se v dataValue vyskytuje řetězec "abc".
        /// Ale taky může obsahovat více řetězců oddělených % !!!
        /// Pro příklad: "abc%789%x"
        /// Což představuje filtr: data musí obsahovat řetězec "abc" a přitom musí obsahovat řetězec "789" a přitom musí obsahovat řetězec "x".
        /// Převod na string je cachován (jeden objekt třídy RowFilterItem je použit na filtrování třeba stotisíckrát !!!)
        /// </summary>
        private List<_FilterContainItem> _FilterContainItems = null;
        /// <summary>
        /// Hodnota filterValue, která je zpracovaná do položek v poli _FilterContainItems, metodou _GetFilterContainItems()
        /// </summary>
        private string _FilterContainItemLast = null; 
        private class _FilterContainItem
        {
            internal _FilterContainItem(bool needContain, string value)
            {
                this.NeedContain = needContain;
                this.Value = value;
            }
            internal readonly bool NeedContain;
            internal readonly string Value;
            /// <summary>
            /// Vrátí true, když daný text (pocházející z dat) vyhovuje této složce filtru "contain".
            /// </summary>
            /// <param name="dataValue"></param>
            /// <returns></returns>
            internal bool ValueIsConform(string dataValue)
            {
                bool contain = (dataValue.IndexOf(this.Value, StringComparison.CurrentCultureIgnoreCase) >= 0);
                return (contain == this.NeedContain);
            }
        }
        #endregion
        #region FILTER COMPARABLE
        /// <summary>
        /// Porovná dva texty a vrátí výsledek (-1: data jsou menší, 0: data jsou stejná, +1: data jsou větší než filterValue).
        /// Pokud jsou obě data převoditelná na číslo, porovná je jako čísla.
        /// Pokud jsou převoditelná na datum a čas, převede a porovná tak.
        /// </summary>
        /// <param name="dataValue"></param>
        /// <param name="filterValue"></param>
        /// <returns></returns>
        private int _FilterCompare(string dataValue, string filterValue)
        {
            // Integer číslo:
            int iData, iFilt;
            if (Int32.TryParse(dataValue, out iData) && Int32.TryParse(filterValue, out iFilt))
                return iData.CompareTo(iFilt);

            // Desetinné číslo?
            decimal dData, dFilt;
            if (Decimal.TryParse(dataValue, out dData) && Decimal.TryParse(filterValue, out dFilt))
                return dData.CompareTo(dFilt);

            // String:
            return dataValue.CompareTo(filterValue);
        }
        #endregion
        #region FILTER IN LIST
        /// <summary>
        /// Začíná hodnota dataValue jako některý z textů v filterValue (texty oddělené středníkem)?
        /// </summary>
        /// <param name="dataValue"></param>
        /// <param name="filterValue"></param>
        /// <returns></returns>
        private bool _FilterIsInList(string dataValue, string filterValue)
		{
			List<string> items = _GetFilterListItem(filterValue);
			foreach (string itm in items)
			{
				if (String.Compare(dataValue, itm, StringComparison.CurrentCultureIgnoreCase) == 0)
					return true;
				if (dataValue.Length > itm.Length && dataValue.StartsWith(itm, StringComparison.CurrentCultureIgnoreCase))
					return true;
			}
			return false;
		}
		/// <summary>
		/// Vrátí seznam prvků z cache nebo z daného textu (a uloží jej do cache)
		/// </summary>
		/// <param name="filterValue"></param>
		/// <returns></returns>
		private List<string> _GetFilterListItem(string filterValue)
		{
			// Jdeme do cache?
			if (this._FilterListLastParsed != null && this._FilterListLastParsed == filterValue && this._FilterListItems != null)
				return this._FilterListItems;

			// Zpracujeme text do cache:
			this._FilterListItems = new List<string>();
			string[] items = filterValue.Split(';');
			foreach (string item in items)
			{
				string itm = item.Trim();
				if (!itm.IsEmpty())
					this._FilterListItems.Add(itm);
			}

			this._FilterListLastParsed = filterValue;

			return this._FilterListItems;
		}
		/// <summary>
		/// Vstupní text filterValue, který byl posledně rozdělen do položek do seznamu _FilterListItems
		/// </summary>
		private string _FilterListLastParsed = null;
		/// <summary>
		/// Cache pro oddělené položky ze seznamu
		/// </summary>
		private List<string> _FilterListItems = null;
		#endregion
		#endregion
    }
    #endregion
    #region ENUMS RowFilterConditionType, ColumnSortMode.
    /// <summary>
    /// Podmínka ve filtru této položky
    /// </summary>
    public enum RowFilterConditionType
    {
        None = 1,
        Equal,
        BeginWith,
        NonBeginWith,
        Contain,
        NonContain,
        NonEqual,
        LowerOrEqual,
        Lower,
        GreaterOrEqual,
        Greater,
        Empty,
        NonEmpty,
        InList,
        InRange
    }
    /// <summary>
    /// Režim třídění dle sloupce
    /// </summary>
    public enum ColumnSortMode
    {
        None = 1,
        Ascending,
        Descending
    }
    #endregion
}
